#include "fibthread.h"
#include "list.h"
#include "sched.h"

#include <errno.h>
#include <string.h>

/* Parametered macro definitions */

#define INITIALIZE_MUTEX_WAIT_LIST(mutex) \
	do { \
		 if (mutex->initialized == FIBTHREAD_MUTEX_STAT_INITIALIZED) { \
			DOUBLY_LIST_INITIALIZE(&mutex->wait_list); \
			mutex->initialized = FIBTHREAD_MUTEX_INITIALIZED; } \
	} while (0)

#define CHECK_FIBTHREAD_MUTEXATTR(attr) \
	(((attr)->type != FIBTHREAD_MUTEX_NORMAL && \
	  (attr)->type != FIBTHREAD_MUTEX_ERRORCHECK && \
	  (attr)->type != FIBTHREAD_MUTEX_RECURSIVE) \
	 ? -1 : 0)

/* External function definitions */

int fibthread_mutex_destroy(fibthread_mutex_t *mutex)
{
	if (mutex->lock_count || mutex->initialized == FIBTHREAD_MUTEX_DESTROYED)
		return EBUSY;

	mutex->lock_count = 0;
	mutex->owner = 0;
	DOUBLY_LIST_INITIALIZE(&mutex->wait_list);
	mutex->attr.type = FIBTHREAD_MUTEX_DEFAULT;
	mutex->initialized = FIBTHREAD_MUTEX_DESTROYED;
	
	return 0;
}

int fibthread_mutex_init(fibthread_mutex_t *mutex, const fibthread_mutexattr_t *attr)
{
	if (!mutex || (attr && CHECK_FIBTHREAD_MUTEXATTR(attr)))
		return EINVAL;

	if (mutex->initialized == FIBTHREAD_MUTEX_INITIALIZED ||
		mutex->initialized == FIBTHREAD_MUTEX_STAT_INITIALIZED)
		return EBUSY;
		
	mutex->lock_count = 0;
	mutex->owner = 0;
	
	if (attr)
		mutex->attr = *attr;
	else
		mutex->attr.type = FIBTHREAD_MUTEX_DEFAULT;
	
	DOUBLY_LIST_INITIALIZE(&mutex->wait_list);

	mutex->initialized = FIBTHREAD_MUTEX_INITIALIZED;
	
	return 0;
}

int fibthread_mutex_lock(fibthread_mutex_t *mutex)
{
	sync_node_t wait_node;

	INITIALIZE_MUTEX_WAIT_LIST(mutex);

	if (mutex->lock_count) {
		switch (mutex->attr.type) {
		case FIBTHREAD_MUTEX_ERRORCHECK:
			if (mutex->owner->id == curr_thread->id)
				return EDEADLK;
			break;
		case FIBTHREAD_MUTEX_RECURSIVE:
			if (mutex->owner->id == curr_thread->id) {
				if (mutex->lock_count == FIBTHREAD_MUTEX_LOCK_MAX)
					return EAGAIN;
				else {
					++mutex->lock_count;
					return 0;
				}
			}
			break;
		}
		
		wait_node.thread = curr_thread;
		DOUBLY_LIST_PUSH_BACK(&mutex->wait_list, &wait_node.node);
		block_thread();
	}

	/* Lock the mutex */

	mutex->owner = curr_thread;
	++mutex->lock_count;

	return 0;
}

int fibthread_mutex_trylock(fibthread_mutex_t *mutex)
{
	INITIALIZE_MUTEX_WAIT_LIST(mutex);

	if (mutex->lock_count) {
		switch (mutex->attr.type) {
		case FIBTHREAD_MUTEX_NORMAL:
		case FIBTHREAD_MUTEX_ERRORCHECK:
			return EBUSY;
		case FIBTHREAD_MUTEX_RECURSIVE:
			if (mutex->owner->id == curr_thread->id)
				if (mutex->lock_count == FIBTHREAD_MUTEX_LOCK_MAX)
					return EAGAIN;
				else {
					++mutex->lock_count;
					return 0;
				}
			else
				return EBUSY;
		}
	}

	/* Lock the mutex */

	mutex->owner = curr_thread;
	++mutex->lock_count;

	return 0;
}

int fibthread_mutex_unlock(fibthread_mutex_t *mutex)
{
	if (!mutex->lock_count || mutex->owner->id != curr_thread->id)
		return EPERM;
	
	if (!--mutex->lock_count) {
		if (IS_LIST_EMPTY(&mutex->wait_list))
			mutex->owner = NULL;
		else {
			doubly_node *tosched_node = DOUBLY_LIST_HEAD(&mutex->wait_list);
			fibthread_struct *tosched_thread = EXTRACT_OBJECT(sync_node_t, node, tosched_node)->thread;

			if (!IS_LIST_ONE_ELEM(&mutex->wait_list)) {
				doubly_node *pos;
				fibthread_struct *tmp_thread;

				WALK_LIST_FWD(&mutex->wait_list, pos) {
					tmp_thread = EXTRACT_OBJECT(sync_node_t, node, &pos)->thread;
					if (tosched_thread->attr.sched_param.sched_priority < tmp_thread->attr.sched_param.sched_priority) {
						tosched_node = pos;
						tosched_thread = tmp_thread;
					}
				}
			}

			DOUBLY_LIST_DELETE_NODE(tosched_node);
			ready_thread(tosched_thread);
		}
	}

	return 0;
}
